/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.python.pydev.analysis.additionalinfo;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListResourceBundle;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.text.IDocument;
import org.python.pydev.core.IModulesManager;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.ModulesKey;
import org.python.pydev.core.docutils.StringUtils;
import org.python.pydev.editor.IPyEditListener;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.codecompletion.revisited.PythonPathHelper;
import org.python.pydev.editor.codecompletion.revisited.modules.AbstractModule;
import org.python.pydev.editor.codecompletion.revisited.modules.SourceModule;
import org.python.pydev.parser.visitors.scope.ASTEntry;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.utils.PyFileListing.PyFileInfo;
import com.aptana.shared_core.io.FileUtils;
import com.python.pydev.util.UIUtils;
/**
* Checks the integrity of the internal pydev caches.
*
* @author Fabio
*/
public class AdditionalInfoIntegrityChecker implements IPyEditListener {
public static class IntegrityInfo {
public boolean allOk = true;
public StringBuffer desc = new StringBuffer();
public IPythonNature nature;
public IModulesManager modulesManager;
public List<ModulesKey> modulesNotInDisk = new ArrayList<ModulesKey>();
public List<ModulesKey> modulesNotInMemory = new ArrayList<ModulesKey>();
public AdditionalProjectInterpreterInfo additionalProjectInfo;
public List<SourceModule> moduleNotInAdditionalInfo = new ArrayList<SourceModule>();
public List<String> additionalModulesNotInDisk = new ArrayList<String>();
public String toString() {
return desc.toString();
}
}
public static IntegrityInfo checkIntegrity(IPythonNature nature, IProgressMonitor monitor, boolean fix)
throws MisconfigurationException {
IntegrityInfo info = new IntegrityInfo();
StringBuffer buffer = info.desc;
info.nature = nature;
info.modulesManager = nature.getAstManager().getModulesManager();
info.additionalProjectInfo = (AdditionalProjectInterpreterInfo) AdditionalProjectInterpreterInfo
.getAdditionalInfoForProject(nature);
if (info.additionalProjectInfo == null) {
buffer.append(com.aptana.shared_core.string.StringUtils.format("Unable to get additional project info for: %s (gotten null)",
nature.getProject()));
info.allOk = false;
}
PythonPathHelper pythonPathHelper = (PythonPathHelper) info.modulesManager.getPythonPathHelper();
List<String> pythonpath = pythonPathHelper.getPythonpath();
buffer.append(com.aptana.shared_core.string.StringUtils
.format("Checking the integrity of the project: %s\n\n", nature.getProject().getName()));
buffer.append("Pythonpath:\n");
for (String string : pythonpath) {
buffer.append(string);
buffer.append("\n");
}
buffer.append("\n");
HashSet<ModulesKey> expectedModuleNames = new HashSet<ModulesKey>();
for (String string : pythonpath) {
File file = new File(string);
if (file.exists() && file.isDirectory()) { //TODO: Handle zip file modules!
Collection<PyFileInfo> modulesBelow = pythonPathHelper.getModulesBelow(file, monitor)
.getFoundPyFileInfos();
for (PyFileInfo fileInfo : modulesBelow) {
File moduleFile = fileInfo.getFile();
String modName = pythonPathHelper.resolveModule(FileUtils.getFileAbsolutePath(moduleFile), true);
if (modName != null) {
expectedModuleNames.add(new ModulesKey(modName, moduleFile));
buffer.append(com.aptana.shared_core.string.StringUtils.format("Found module: %s - %s\n", modName, moduleFile));
} else {
if (PythonPathHelper.isValidModuleLastPart(StringUtils.stripExtension((moduleFile.getName())))) {
info.allOk = false;
buffer.append(com.aptana.shared_core.string.StringUtils.format(
"Unable to resolve module: %s (gotten null module name)\n", moduleFile));
}
}
}
} else {
info.allOk = false;
buffer.append(com.aptana.shared_core.string.StringUtils.format("File %s is referenced in the pythonpath but does not exist.", file));
}
}
check(expectedModuleNames, info, fix);
return info;
}
/**
* @param expectedModuleNames the modules that exist in the disk (an actual file is found and checked for the module it resolves to)
* @throws MisconfigurationException
*/
private static void check(HashSet<ModulesKey> expectedModuleNames, IntegrityInfo info, boolean fix)
throws MisconfigurationException {
StringBuffer buffer = info.desc;
ModulesKey[] onlyDirectModules = info.modulesManager.getOnlyDirectModules();
TreeSet<ModulesKey> inModulesManager = new TreeSet<ModulesKey>(Arrays.asList(onlyDirectModules));
Set<String> allAdditionalInfoTrackedModules = info.additionalProjectInfo.getAllModulesWithTokens();
for (ModulesKey key : inModulesManager) {
if (!expectedModuleNames.contains(key)) {
info.allOk = false;
info.modulesNotInDisk.add(key);
buffer.append(com.aptana.shared_core.string.StringUtils.format("ModulesKey %s exists in memory but not in the disk.\n", key));
}
}
for (String s : allAdditionalInfoTrackedModules) {
if (!expectedModuleNames.contains(new ModulesKey(s, null))) {
info.allOk = false;
info.additionalModulesNotInDisk.add(s);
buffer.append(com.aptana.shared_core.string.StringUtils.format(
"The module %s exists in the additional info memory but not in the disk.\n", s));
}
}
for (ModulesKey key : expectedModuleNames) {
if (!inModulesManager.contains(key)) {
info.allOk = false;
info.modulesNotInMemory.add(key);
buffer.append(com.aptana.shared_core.string.StringUtils.format("ModulesKey %s exists in the disk but not in memory.\n", key));
}
if (!allAdditionalInfoTrackedModules.contains(key.name)) {
try {
AbstractModule mod = AbstractModule.createModule(key.name, key.file, info.nature, true);
if (!(mod instanceof SourceModule)) {
continue;
}
SourceModule module = (SourceModule) mod;
if (module == null || module.getAst() == null) {
buffer.append(com.aptana.shared_core.string.StringUtils.format(
"Warning: cannot parse: %s - %s (so, it's ok not having additional info on it)\n",
key.name, key.file));
} else {
try {
Iterator<ASTEntry> innerEntriesForAST = AbstractAdditionalDependencyInfo
.getInnerEntriesForAST(module.getAst()).o2;
if (innerEntriesForAST.hasNext()) {
info.allOk = false;
info.moduleNotInAdditionalInfo.add(module);
buffer.append(com.aptana.shared_core.string.StringUtils.format(
"The additional info index of the module: %s is not updated.\n", key.name));
}
} catch (Exception e) {
buffer.append(com.aptana.shared_core.string.StringUtils.format("Unexpected error happened on: %s - %s: %s\n", key.name,
key.file, e.getMessage()));
}
}
} catch (IOException e) {
//OK, it cannot be parsed, so, we cannot generate its info
buffer.append(com.aptana.shared_core.string.StringUtils.format(
"Warning: cannot parse: %s - %s (so, it's ok not having additional info on it)\n",
key.name, key.file));
}
}
}
if (info.allOk) {
buffer.append("All checks OK!\n");
} else {
if (fix) {
buffer.append("Fixing:\n");
//modules manager
buffer.append(com.aptana.shared_core.string.StringUtils.format("Removing modules from memory: %s\n", info.modulesNotInDisk));
info.modulesManager.removeModules(info.modulesNotInDisk);
buffer.append(com.aptana.shared_core.string.StringUtils.format("Adding to memory modules: %s\n", info.modulesNotInMemory));
for (ModulesKey key : info.modulesNotInMemory) {
buffer.append("Adding modules ...\n");
info.modulesManager.addModule(key);
}
//additional info
buffer.append(com.aptana.shared_core.string.StringUtils
.format("Removing from additional info: %s\n", info.additionalModulesNotInDisk));
for (String s : info.additionalModulesNotInDisk) {
info.additionalProjectInfo.removeInfoFromModule(s, true);
}
buffer.append(com.aptana.shared_core.string.StringUtils.format("Adding to additional info modules found in disk: %s\n",
info.moduleNotInAdditionalInfo));
for (SourceModule mod : info.moduleNotInAdditionalInfo) {
info.additionalProjectInfo.addAstInfo(mod.getAst(), mod.getModulesKey(), true);
}
}
}
}
public void onCreateActions(ListResourceBundle resources, final PyEdit edit, IProgressMonitor monitor) {
edit.addOfflineActionListener("--internal-test-modules", new Action() {
@Override
public void run() {
List<IPythonNature> allPythonNatures = PythonNature.getAllPythonNatures();
StringBuffer buf = new StringBuffer();
try {
for (IPythonNature nature : allPythonNatures) {
buf.append(checkIntegrity(nature, new NullProgressMonitor(), true));
}
} catch (MisconfigurationException e) {
buf.append(e.getMessage());
}
UIUtils.showString(buf.toString());
}
}, "Used just for testing (do not use).", true);
}
public void onDispose(PyEdit edit, IProgressMonitor monitor) {
}
public void onSave(PyEdit edit, IProgressMonitor monitor) {
}
public void onSetDocument(IDocument document, PyEdit edit, IProgressMonitor monitor) {
}
}